﻿using System;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using NetMicro.Core.ConfigureOptions.Attributes;
using NetMicro.Core.Helper;

namespace NetMicro.Core.ConfigureOptions
{
    /// <summary>
    /// 可变选项服务拓展类
    /// </summary>

    public static class ServiceCollectionExtensions
    {
        /// <summary>
        /// 添加选项配置
        /// </summary>
        /// <typeparam name="TOptions"></typeparam>
        /// <param name="services"></param>
        /// <returns></returns>
        public static IServiceCollection AddConfigure<TOptions>(this IServiceCollection services)
            where TOptions : class
        {
            Type optionsType = typeof(TOptions);
            ConfigAttribute optionsSettings = optionsType.GetCustomAttribute<ConfigAttribute>(false);

            IConfiguration configurationRoot = App.Configuration;

            // 获取键名
            string jsonKey = GetOptionsJsonKey(optionsSettings, optionsType);
            IConfigurationSection optionsConfiguration = configurationRoot.GetSection(jsonKey);

            // 配置选项监听
            if (typeof(IConfigOptionsListener<TOptions>).IsAssignableFrom(optionsType))
            {
                MethodInfo onListenerMethod = optionsType.GetMethod(nameof(IConfigOptionsListener<TOptions>.OnListener));
                if (onListenerMethod != null)
                {
                    ChangeToken.OnChange(() => configurationRoot.GetReloadToken(), () =>
                    {
                        TOptions options = optionsConfiguration.Get<TOptions>();
                        onListenerMethod.Invoke(options, new object[] { options, optionsConfiguration });
                    });
                }
            }

            services.AddOptions<TOptions>()
                .Bind(optionsConfiguration, options =>
                {
                    options.BindNonPublicProperties = true; // 绑定私有变量
                })
                .ValidateDataAnnotations();

            // 配置复杂验证后后期配置
            Type validateInterface = optionsType.GetInterfaces()
                .FirstOrDefault(u => u.IsGenericType && typeof(IConfigOptions).IsAssignableFrom(u.GetGenericTypeDefinition()));
            if (validateInterface != null)
            {
                Type[] genericArguments = validateInterface.GenericTypeArguments;

                // 配置复杂验证
                if (genericArguments.Length > 1)
                {
                    services.TryAddEnumerable(ServiceDescriptor.Singleton(typeof(IValidateOptions<TOptions>), genericArguments.Last()));
                }

                // 配置后期配置
                MethodInfo postConfigureMethod = optionsType.GetMethod(nameof(IConfigOptions<TOptions>.PostConfigure));
                if (postConfigureMethod != null)
                {
                    if (optionsSettings?.PostConfigureAll != true)
                        services.PostConfigure<TOptions>(options => postConfigureMethod.Invoke(options, new object[] { options, optionsConfiguration }));
                    else
                        services.PostConfigureAll<TOptions>(options => postConfigureMethod.Invoke(options, new object[] { options, optionsConfiguration }));
                }
            }

            return services;
        }

        /// <summary>
        /// 获取选项键
        /// </summary>
        /// <param name="config">选项配置特性</param>
        /// <param name="optionsType">选项类型</param>
        /// <returns></returns>
        private static string GetOptionsJsonKey(ConfigAttribute config, Type optionsType)
        {
            // 默认后缀
            string defaultStuffx = nameof(Options);

            return config switch
            {
                // // 没有贴 [ConfigAttribute]，如果选项类以 `Options` 结尾，则移除，否则返回类名称
                null => optionsType.Name.EndsWith(defaultStuffx) ? optionsType.Name[0..^defaultStuffx.Length] : optionsType.Name,
                // 如果贴有 [ConfigAttribute] 特性，但未指定 JsonKey 参数，则直接返回类名，否则返回 JsonKey
                _ => config != null && string.IsNullOrWhiteSpace(config.JsonKey) ? optionsType.Name : config.JsonKey,
            };
        }
    }
}